import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { JwtService } from "@nestjs/jwt";
import { ClsService } from "nestjs-cls";
import { Decorator } from "@/common/enum/decorator.enum";
import { Role } from "@/common/enum/role.enum";

@Injectable()
export class AuthGuard implements CanActivate {
    constructor(
        private cls: ClsService,
        private jwtService: JwtService,
        private reflector: Reflector,
    ) {}

    async canActivate(context: ExecutionContext): Promise<boolean> {
        // 反射和元数据参考: https://nest.nodejs.cn/fundamentals/execution-context#%E5%8F%8D%E5%B0%84%E5%92%8C%E5%85%83%E6%95%B0%E6%8D%AE
        const isPublic = this.reflector.getAllAndOverride<boolean>(Decorator.IS_PUBLIC_KEY, [
            context.getHandler(),
            context.getClass(),
        ]);

        if (isPublic) {
            return true;
        } else {
            const request = context.switchToHttp().getRequest();
            const tokenPayload = await this.validateToken(request);
            const hasRole = this.validateRoles(context, tokenPayload);
            if (!hasRole) {
                throw new UnauthorizedException("暂无权限, 请联系管理员操作");
            }
            return true;
        }
    }

    /** 校验角色权限 */
    private validateRoles(context: ExecutionContext, tokenPayload: any): boolean {
        const requiredRoles = this.reflector.getAllAndOverride<Role[]>(Decorator.ROLE_KEY, [
            context.getHandler(),
            context.getClass(),
        ]);
        if (!requiredRoles) {
            return true;
        }
        const roles = tokenPayload.roles as string[];
        return requiredRoles.some((role) => roles?.includes(role));
    }

    /** 校验token并返回载荷 */
    private async validateToken(request: any): Promise<any> {
        const [type, token] = request.headers.authorization?.split(" ") ?? [];
        let payload = null;
        try {
            // verify 方法在验证时, 错误的 token 会抛出异常
            payload = await this.jwtService.verifyAsync(token, {
                secret: process.env.JWT_SECRET,
            });
            request.user = payload; // 将有效载荷赋值给请求对象, 这样可以在路由处理器中访问它
            this.cls.set("user", payload); // 把当前用户存到隔离的 CLS 上下文
        } catch {
            throw new UnauthorizedException();
        }
        return type === "Bearer" ? payload : null;
    }
}
